#pragma once

#include "base.h"
#include "detail/s4.h"
#include "../container/robuf.h"
#include "../impl/m4.h"

namespace mdtx::online::indicator
{
    // robust

    /**
     * @brief Quantile based rolling kurtosis
     *
     * @tparam T scala type
     */
    template <typename T>
    class kurt_quantile : public indi_base<T, T, 1, 4>
    {
        using base = indi_base<T, T, 1, 4>;

    public:
        using typename base::tuple_in;
        using typename base::tuple_out;
        using base::update;

    protected:
        container::quantile_robuf<T, 7> buf;

    public:
        explicit kurt_quantile(ptrdiff_t period)
            : buf(period, std::array<T, 7>{0.125, 0.25, 0.375, 0.5, 0.625, 0.75, 0.875})
        {
        }

        /**
         * @brief update
         *
         * @param in x
         * @return tuple_out median, iqr, skewness, kurtosis
         */
        tuple_out update(tuple_in const &in) override
        {
            buf.insert(std::get<0>(in));

            T iqr{}, skew{}, kurt{};
            auto [e1, e2, e3, e4, e5, e6, e7] = buf.get();
            if (buf.full())
            {
                iqr = e6 - e2;
                skew = ((e6 - e4) + (e2 - e4)) / iqr;
                kurt = ((e7 - e5) + (e3 - e1)) / iqr;
            }
            else
            {
                e4 = buf.sorted[buf.buf.size() / 2];
            }

            return tuple_out(e4, iqr, skew, kurt);
        }
    };

    // not robust

    /**
     * @brief Rolling kurtosis, G2 coefficient
     *
     * @tparam T scala type
     */
    template <typename T>
    class kurt_G2 : public indi_base<T, T, 1, 4>
    {
        using base = indi_base<T, T, 1, 4>;

    public:
        using typename base::tuple_in;
        using typename base::tuple_out;
        using base::update;

    protected:
        boost::circular_buffer<T> buf;
        impl::m4<T> moment{};
        T co_sd{}, co_sk{}, co_ku{};

    public:
        explicit kurt_G2(ptrdiff_t period)
            : buf(period * 4)
        {
        }

        /**
         * @brief update
         *
         * @param in x
         * @return tuple_out mean, standard deviation, skewness, kurtosis
         */
        tuple_out update(tuple_in const &in) override
        {
            T x = std::get<0>(in);
            T x2 = x * x;
            T x3 = x2 * x;
            T x4 = x2 * x2;

            if (buf.full())
            {
                moment.roll_impl(x, x2, x3, x4, buf[0], buf[1], buf[2], buf[3]);
            }
            else
            {
                moment.insert_impl(x, x2, x3, x4);
                if (moment.n > 1)
                {
                    co_sd = detail::coefs_sd(moment.n);
                    if (moment.n > 2)
                    {
                        co_sk = detail::coefs_sk(moment.n);
                        if (moment.n > 3)
                        {
                            co_ku = detail::coefs_ku(moment.n);
                        }
                    }
                }
            }
            bpush(buf, x, x2, x3, x4);

            T sd{}, skew{}, kurt{};
            auto [m, m2, m3, m4] = moment.center_impl();
            if (m2 > 0)
            {
                sd = co_sd * std::sqrt(m2);
                T g1 = m3 / std::pow(m2, 1.5);
                skew = co_sk * g1;
                T g2 = m4 / (m2 * m2) - 3;
                kurt = co_ku * ((moment.n + 1) * g2 + 6);
            }

            return tuple_out(m, sd, skew, kurt);
        }
    };

    /**
     * @brief Cumulative kurtosis, G2 coefficient
     *
     * @tparam T scala type
     */
    template <typename T>
    class kurt_G2cu : public indi_base<T, T, 1, 4>
    {
        using base = indi_base<T, T, 1, 4>;

    public:
        using typename base::tuple_in;
        using typename base::tuple_out;
        using base::update;

    protected:
        impl::m4<T> moment;
        T co_sd, co_sk, co_ku;

    public:
        /**
         * @brief update
         *
         * @param in x
         * @return tuple_out mean, standard deviation, skewness, kurtosis
         */
        tuple_out update(tuple_in const &in) override
        {
            T x = std::get<0>(in);
            T x2 = x * x;
            T x3 = x2 * x;
            T x4 = x2 * x2;

            moment.insert_impl(x, x2, x3, x4);
            if (moment.n > 1)
            {
                co_sd = detail::coefs_sd(moment.n);
                if (moment.n > 2)
                {
                    co_sk = detail::coefs_sk(moment.n);
                    if (moment.n > 3)
                    {
                        co_ku = detail::coefs_ku(moment.n);
                    }
                }
            }

            T sd{}, skew{}, kurt{};
            auto [m, m2, m3, m4] = moment.center_impl();
            if (m2 > 0)
            {
                sd = co_sd * std::sqrt(m2);
                T g1 = m3 / std::pow(m2, 1.5);
                skew = co_sk * g1;
                T g2 = m4 / (m2 * m2) - 3;
                kurt = co_ku * ((moment.n + 1) * g2 + 6);
            }

            return tuple_out(m, sd, skew, kurt);
        }
    };
}
